/*
 * Copyright 2011-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.mgmt.commons.util;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 日期工具类
 *
 * @author liym
 * @author zyj8822
 * @since 2018-06-08 15:27 新建
 */
public final class DateUtil {

    /**
     * yyyy-MM-dd HH:mm:ss
     */
    private final static DateFormat FORMAT_DATE_TIME = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * yyyy-MM-dd
     */
    private final static DateFormat FORMAT_DATE = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * yyyy-MM-dd EEE
     */
    private final static DateFormat FORMAT_DATE_WEEK = new SimpleDateFormat("yyyy-MM-dd EEE");

    private DateUtil() {}

    /**
     * 获取当前系统纳秒时间
     *
     * @return 系统纳秒时间
     */
    public static long nanoTime() {
        return System.nanoTime();
    }

    /**
     * 获取当前时间
     *
     * <p>yyyy-MM-dd HH:mm:ss</p>
     *
     * @return yyyy-MM-dd HH:mm:ss
     * @since 2014-10-09 15:08:11
     */
    public static String getDatetime() {
        return FORMAT_DATE_TIME.format(new Date());
    }

    /**
     * 解析字符串为Date
     *
     * <p>yyyy-MM-dd HH:mm:ss</p>
     *
     * @param date 字符串
     * @return yyyy-MM-dd HH:mm:ss
     * @since 2014-10-09 15:12:00
     */
    public static Date parseDateTime(String date) {
        try {
            return FORMAT_DATE_TIME.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException("不能将字符串解析为日期，请检查格式是否正确：" + date);
        }
    }

    /**
     * 解析字符串为Date
     *
     * <p>yyyy-MM-dd</p>
     *
     * @param date 日期
     * @return yyyy-MM-dd
     * @author liym
     * @since 2016-07-20 10:56:28
     */
    public static Date parseDate(String date) {
        try {
            return FORMAT_DATE.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException("不能将字符串解析为日期，请检查格式是否正确：" + date);
        }
    }

    /**
     * 格式化日期，解决从MySQL中查出来的日期后面多了“.0”的问题
     *
     * <p>2014-10-09 15:15:12</p>
     *
     * @param date Date
     * @return 2014-10-09 15:15:12
     * @since 2014-10-09 15:15:12
     */
    public static String formatDateTime(Date date) {
        return FORMAT_DATE_TIME.format(date);
    }

    /**
     * 格式化日期
     * <p>yyyy-MM-dd</p>
     *
     * @param date 日期
     * @return yyyy-MM-dd
     * @author liym
     * @since 2016-07-20 11:00:43
     */
    public static String formatDate(Date date) {
        return FORMAT_DATE.format(date);
    }

    /**
     * 格式化日期，包含星期
     *
     * <p>yyyy-MM-dd EEE</p>
     *
     * @param date 日期
     * @return 2016-06-27 星期一
     * @author liym
     * @since 2016-07-20 11:32:16
     */
    public static String formatDateWeek(Date date) {
        return FORMAT_DATE_WEEK.format(date);
    }

    /**
     * 得到两个日期之间的月份（yyyy-MM）
     *
     * @param start 开始日期（yyyy-MM-dd）
     * @param end 结束日期（yyyy-MM-dd）
     * @return 月份
     * @author liym
     * @since 2014-12-07 11:36:47
     */
    public static List<String> getMonthsBetween(String start, String end) {
        Date date1 = new Date(), date2 = new Date();
        try {
            date1 = FORMAT_DATE.parse(start);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        try {
            date2 = FORMAT_DATE.parse(end);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        Calendar c1 = Calendar.getInstance();
        Calendar c2 = Calendar.getInstance();

        List<String> monthList = new ArrayList<>();
        c1.setTime(date1);
        c2.setTime(date2);
        while (c1.compareTo(c2) < 0) {
            // 开始日期加一个月直到等于结束日期为止
            c1.add(Calendar.MONTH, 1);
            Date ss = c1.getTime();
            String str = getMonth(ss);
            monthList.add(str);
        }
        int dayOfMonth = c1.get(Calendar.DAY_OF_MONTH);
        // 如果第一个日期是一个月的第一天，则将第一个日期也包括。
        if (dayOfMonth == 1) {
            monthList.add(0, getMonth(date1));
        }

        if (monthList.isEmpty()) {
            monthList.add(getMonth(date1));
        }

        return monthList;
    }

    private static String getMonth(Date date) {
        String str = FORMAT_DATE.format(date);
        str = str.substring(0, str.lastIndexOf("-"));
        return str;
    }

    /**
     * 判断是否为闰年
     *
     * <p>置闰法则：四年一闰；百年不闰，四百年再闰</p>
     *
     * <p>
     * 精确计算方法：（按一回归年365天5小时48分45.5秒）
     * <ol>
     * <li>普通年能被4整除且不能被100整除的为闰年。（如2004年就是闰年，1900年不是闰年）</li>
     * <li>世纪年能被400整除的是闰年。（如2000年是闰年，1900年不是闰年）</li>
     * <li>对于数值很大的年份，这年如果能整除3200，并且能整除172800则是闰年。如172800年是闰年，86400年不是闰年。<br>
     * （因为虽然能整除3200，但不能整除172800）（此按一回归年365天5小时48分45.5秒计算）</li>
     * </ol>
     * </p>
     *
     * @param year 年份
     * @return true/false
     * @author liym
     * @see sun.util.calendar.CalendarUtils#isGregorianLeapYear(int)
     * @since 2016-08-11 09:56:30
     */
    public static boolean isLeapYear(int year) {
        return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
    }

    /**
     * 获取指定年份，指定月份的天数
     *
     * @param monthNum 月份：0～11，值与 {@link java.util.Calendar#MONTH} 相同
     * @param year 年份
     * @return 月份的天数
     * @author liym
     * @see java.util.GregorianCalendar#monthLength(int, int)
     * @since 2016-08-11 11:33:46
     */
    public static int getLastDayOfMonth(final int monthNum, final int year) {
        if (monthNum < 0 || monthNum > 11) {
            throw new IllegalArgumentException("Illegal month number: " + monthNum + ", valid is 0 to 11, inclusive");
        }
        // 平年
        int[] monthLength = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        // 闰年
        int[] leapMonthLength = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

        return isLeapYear(year) ? leapMonthLength[monthNum] : monthLength[monthNum];
    }

}
